use std::{sync::Once, thread, time::Duration};

use smallvec::smallvec;
use tracing::debug;

use crate::{
    ipc::{
        framer,
        message::{Argument, ArgumentFlag, Request, Response},
        peer::{Client, Server},
        transport,
    },
    sys::shmem,
};

fn init_test_logger() {
    static INIT_LOGGER: Once = Once::new();

    INIT_LOGGER.call_once(|| {
        let _ = tracing_subscriber::fmt()
            .with_max_level(tracing::Level::DEBUG)
            .with_thread_ids(true)
            .with_thread_names(false)
            .with_file(false)
            .with_target(false)
            .with_writer(std::io::stdout)
            .try_init();
    });
}

#[test]
fn test_invoke() {
    mod method_id {
        pub const ADD_U64: u64 = 0xCAFE;
        pub const SHUTDOWN: u64 = 0xFFFF;
    }

    const CONN_TIMEOUT: Duration = Duration::from_millis(200);

    self::init_test_logger();

    let transport = transport::Transport::new(4096 * 1024);
    let framer = framer::Framer::new(4096);
    let addr = shmem::unique_name();

    let server = Server::create(&transport, framer, &addr).unwrap();
    debug!("{:#?}", server);

    let client = Client::connect(&transport, framer, &addr, CONN_TIMEOUT).unwrap();
    debug!("{:#?}", client);

    let server_thread = std::thread::spawn(move || {
        debug!("[Server] Thread started");

        while let Ok(Some(request)) = server.receive_message::<Request>() {
            debug!(
                "[Server] Received request: request_id={}, method_id={}, argc={}",
                request.request_id(),
                request.method_id(),
                request.argc()
            );

            match request.method_id() {
                method_id::ADD_U64 => {
                    let lhs = request.args()[0]
                        .downcast::<u64>()
                        .expect("Invalid argument type");
                    let rhs = request.args()[1]
                        .downcast::<u64>()
                        .expect("Invalid argument type");

                    let result = lhs + rhs;
                    let response = Response::with_request(
                        &request,
                        Argument::from_value(result, ArgumentFlag::default()),
                    );
                    debug!(
                        "[Server] Sending response: request_id={}, method_id={}",
                        response.request_id(),
                        response.method_id(),
                    );
                    server
                        .send_message(&response)
                        .expect("Failed to send response");
                }
                method_id::SHUTDOWN => {
                    let response = Response::with_request(&request, Argument::empty());
                    debug!(
                        "[Server] Sending response: request_id={}, method_id={}",
                        response.request_id(),
                        response.method_id(),
                    );
                    server
                        .send_message(&response)
                        .expect("Failed to send response");

                    debug!("[Server] Received shutdown request, shutting down...");
                    break;
                }
                _ => {
                    debug!(
                        "[Server] Request {}: Unknown method {}",
                        request.request_id(),
                        request.method_id(),
                    );
                    break;
                }
            }
        }

        thread::sleep(Duration::from_millis(200));
        debug!("[Server] Thread finished");
    });

    {
        let mut value = 0u64;
        for i in 1..=100u64 {
            debug!("[Client] Invoking 'server::add_u64({}, {})'...", value, i);
            let request = Request::with_args(
                method_id::ADD_U64,
                smallvec![
                    Argument::from_value(value, ArgumentFlag::ARG_IN),
                    Argument::from_ref(&i, ArgumentFlag::ARG_IN),
                ],
            );
            let response = client.invoke(&request).expect("Invoke failed");

            let ret_val = response
                .ret_value()
                .downcast::<u64>()
                .expect("Invalid argument type");
            debug!(
                "[Client] Result 'server::add_u64({}, {})' is {}",
                value, i, ret_val
            );

            value = ret_val;
        }
    }

    {
        let request = Request::empty(method_id::SHUTDOWN);
        debug!("[Client] Invoking 'server::shutdown()'...");
        let response = client.invoke(&request).expect("Invoke failed");
        let ret_val = response
            .ret_value()
            .downcast::<()>()
            .expect("Invalid argument type");
        debug!("[Client] Result 'server::shutdown()' is {:?}", ret_val);
    }

    let _ = server_thread.join();
}
